/*
 * Copyright (c) JForum Team
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met:
 * 
 * 1) Redistributions of source code must retain the above 
 * copyright notice, this list of conditions and the 
 * following  disclaimer.
 * 2)  Redistributions in binary form must reproduce the 
 * above copyright notice, this list of conditions and 
 * the following disclaimer in the documentation and/or 
 * other materials provided with the distribution.
 * 3) Neither the name of "Rafael Steil" nor 
 * the names of its contributors may be used to endorse 
 * or promote products derived from this software without 
 * specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT 
 * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
 * IN CONTRACT, STRICT LIABILITY, OR TORT 
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
 * 
 * Created on 02/07/2005 13:18:34
 * The JForum Project
 * http://www.jforum.net
 */
package net.jforum.dao.mysql.security;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.jforum.JForumExecutionContext;
import net.jforum.dao.generic.security.GenericGroupSecurityDAO;
import net.jforum.dao.generic.security.SecurityCommon;
import net.jforum.exceptions.DatabaseException;
import net.jforum.security.RoleCollection;
import net.jforum.util.DbUtils;
import net.jforum.util.preferences.SystemGlobals;

import com.mysql.jdbc.Connection;
import com.mysql.jdbc.Statement;

/**
 * MySQL 3.23 hacks based on Andy's work
 * 
 * @author Rafael Steil
 * @version $Id: MySQL323GroupSecurityDAO.java,v 1.9 2007/08/16 13:07:33
 *          rafaelsteil Exp $
 */
public class MySQL323GroupSecurityDAO extends GenericGroupSecurityDAO {
    /**
     * @see net.jforum.dao.generic.security.GenericGroupSecurityDAO#loadRoles(int[])
     */
    protected RoleCollection loadRoles(int[] groupIds) {
	String groupIdAsString = SecurityCommon.groupIdAsString(groupIds);

	RoleCollection roleCollection = new RoleCollection();

	PreparedStatement rolesP = null;
	PreparedStatement roleValuesP = null;
	ResultSet roles = null;
	ResultSet roleValues = null;

	try {
	    // Roles
	    String sql = this.sqlWithGroups("PermissionControl.getRoles",
		    groupIdAsString);

	    rolesP = JForumExecutionContext.getConnection().prepareStatement(
		    sql);
	    roles = rolesP.executeQuery();

	    // RoleValues
	    sql = this.sqlWithGroups("PermissionControl.getRoleValues",
		    groupIdAsString);

	    roleValuesP = JForumExecutionContext.getConnection()
		    .prepareStatement(sql);
	    roleValues = roleValuesP.executeQuery();

	    MySQL323RoleResultSet mergedRs = new MySQL323RoleResultSet(0, 0,
		    null, null);
	    mergedRs.merge(roles, roleValues);

	    roleCollection = SecurityCommon.loadRoles(mergedRs);
	} catch (Exception e) {
	    throw new DatabaseException(e);
	} finally {
	    DbUtils.close(roles, rolesP);
	    DbUtils.close(roleValues, roleValuesP);
	}

	return roleCollection;
    }

    private String sqlWithGroups(String queryName, String groups) {
	String sql = SystemGlobals.getSql(queryName);

	if ("".equals(groups)) {
	    // We suppose there is no "negative" group ids
	    sql = sql.replaceAll("#IN#", "-1");
	} else {
	    sql = sql.replaceAll("#IN#", groups);
	}

	return sql.replaceAll("#IN#", groups);
    }

    /**
     * @see net.jforum.dao.security.SecurityDAO#deleteAllRoles(int)
     */
    public void deleteAllRoles(int id) {
	PreparedStatement p = null;
	try {
	    // First, get the set of role ids
	    p = JForumExecutionContext
		    .getConnection()
		    .prepareStatement(
			    SystemGlobals
				    .getSql("PermissionControl.getRoleIdsByGroup"));
	    p.setInt(1, id);

	    String roleIds = this.getCsvIdList(p);

	    p.close();

	    if (roleIds.length() > 0) {
		// Then remove all matching values
		p = this.getStatementForCsv(SystemGlobals
			.getSql("PermissionControl.deleteRoleValuesByRoleId"),
			roleIds);
		p.executeUpdate();
		p.close();
	    }

	    // Now delete the group roles
	    p = JForumExecutionContext.getConnection().prepareStatement(
		    SystemGlobals
			    .getSql("PermissionControl.deleteAllGroupRoles"));
	    p.setInt(1, id);
	    p.executeUpdate();
	} catch (SQLException e) {
	    throw new DatabaseException(e);
	} finally {
	    DbUtils.close(p);
	}
    }

    /**
     * Gets a statement to use with some csv data
     * 
     * @param sql
     *            The SQL query to execute. It must have an "?", which will be
     *            replaced by <code>csv</code>
     * @param csv
     *            The ids to replace
     * @return The statement, ready to execute
     * @throws SQLException
     */
    protected PreparedStatement getStatementForCsv(String sql, String csv)
	    throws SQLException {
	int index = sql.indexOf('?');
	sql = sql.substring(0, index) + csv + sql.substring(index + 1);
	return JForumExecutionContext.getConnection().prepareStatement(sql);
    }

    /**
     * Gets a set of ids from a statement The statement is expected to return an
     * id in the first column
     * 
     * @param p
     *            The statement to execute
     * @return The ids, separated by comma
     * @throws SQLException
     */
    protected String getCsvIdList(PreparedStatement p) throws SQLException {
	ResultSet rs = p.executeQuery();

	StringBuffer sb = new StringBuffer();

	while (rs.next()) {
	    sb.append(rs.getInt(1)).append(",");
	}

	sb.append("-1");

	rs.close();

	return sb.toString();
    }

    private static class MySQL323RoleResultSet extends com.mysql.jdbc.ResultSet {
	private Map currentEntry;
	private Iterator dataIterator;
	private List data = new ArrayList();

	public MySQL323RoleResultSet(long updateCount, long updateId,
		Connection connection, Statement statement) {
	    super(updateCount, updateId, connection, statement);
	}

	public void merge(ResultSet roles, ResultSet roleValues)
		throws SQLException {
	    this.fillDataFromRs(roles);
	    this.fillDataFromRs(roleValues);

	    this.dataIterator = this.data.iterator();
	}

	private void fillDataFromRs(ResultSet rs) throws SQLException {
	    while (rs.next()) {
		Map m = new HashMap();

		m.put("name", rs.getString("name"));
		m.put("role_value", rs.getString("role_value"));

		this.data.add(m);
	    }
	}

	/**
	 * @see com.mysql.jdbc.ResultSet#next()
	 */
	public boolean next() throws SQLException {
	    boolean hasNext = this.dataIterator.hasNext();

	    if (hasNext) {
		this.currentEntry = (Map) this.dataIterator.next();
	    }

	    return hasNext;
	}

	/**
	 * @see com.mysql.jdbc.ResultSet#getString(java.lang.String)
	 */
	public String getString(String column) throws SQLException {
	    return (String) this.currentEntry.get(column);
	}

	/**
	 * Always returns false
	 */
	public boolean wasNull() throws SQLException {
	    return false;
	}

	/**
	 * Does nothing
	 */
	public void close() throws SQLException {
	}
    }
}
